كيفية إرسال بريد إلكتروني بدوال

 Winsock API

 

 

في البداية يجب أن تعرف إن الموضوع ليس في إرسال البريد الإلكتروني من لغة برمجة معينة.. لكن الموضوع يكمن في كيفية  استخدام بروتوكول SMTP من أي لغة برمجة لمخاطبة خادم البريد المرسل إليه وكيف ترد عليه إذا أعطاك رسالة معينة وهكذا ..

 

وفيما يلي شرح مبسط لكيفية استخدام بروتوكول SMTP  

 

 

 

 

Simple Mail Transfer Protocol (SMTP) :

 

كثيرا ما تسمع أسم  هذا البروتوكول وأنت تتعامل مع برامج الهاك بالذات .. حيث يستخدم هذا البروتوكول في إرسال أيميل عن طريق الاتصال بسيرفر الموقع SMTP Server - الموقع المضيف للأيميل أي المراد إرسال البريد الإلكتروني إلي إيميل مسجل علي السيرفر  نفسه - علي المنفذ 25 ... في الواقع أنت ها هنا تتصل مع برنامج يدعم بروتوكول SMTP مثل Mdaemon أو أي برنامج أخر موجود علي السيرفر ويستخدم لإدارة حركة الاتصال بين أي عميل والسيرفر عن طريق هذا البروتوكول... حيث البرنامج هو الذي يرد عليك .. في كل خطوة..

 

وبروتوكول SMTP يدعم مجموعة خطوات واحدة لإرسال أي بريد.. أي أن يلعب دور المنظم لعملية الإرسال لبريد معين..

 

هناك أسلوب قديم نوعا لإرسال رسائل مزيفة إلى أيميل ما بطريقة يدوية .

 

حيث تكتب من أل Telnet في نظام النوافذ Windows)) في خانتي Host و port عنوان خادم البريد والمنفذ 25.ولاحظ أنك لن تري ما تكتبه في تطبيق Telnet وبالتالي يجب تشغيل خدمة local echo  من الإعدادات Preferences .

 

Telnet  mail.hotmail.com 25

=========================================

helo Hotmail

Rem اسم أل أيميل المرسل منه

MAIL FROM : <Thensync@hotmail.com>

Rem  فيرد علينا خادم البريد بالرسالة التالية 

250 Thensync@hotmail.com... Sender ok

Rem ثم نكتب أسم البريد المستهدف

RCPT TO: <Victim Mail@hotmail.com>

Rem ويرد علينا خادم البريد  بالرسالة التالية

250 victim Mail@hotmail.com... Recipient ok

Rem  Enter على سطر لوحدها ونضغط  dataثم نكتب

data  

Rem فيكون رد خادم الموقع هو

354 Enter mail, end with "." on a line by itself

Rem   وإذا ظهر رد الخادم السابق فتكتب  عنوان الرسالة ثم نص الرسالة 

Subject: Test Fake Mail

Hi , I am Testing A fake mail

Rem  لإرسالها Enterوتكتب نقطة على سطر لوحدها لإنهاء الرسالة وتضغط

.

Rem ويرد عليك خادم البريد بالرد التالي إذا تمكن من  إرسال الرسالة

250 CAA15314 Message accepted for delivery

Remومن ثم لو أردت الخروج تكتب

Quit

=========================================

الأرقام التي تسبق الردود مثل 250 هي أرقام تعريفية للرسائل Message ID أو (MID) وستجد لها استخدام مهم جدا في موضوعنا  هذا..

وإذا لخصت لك طريقة إرسال أل أيميل باستخدام بروتوكول SMTP  مرة أخرى من أل Telnet :

MAIL FROM: <Thensync@hotmail.com>

RCPT TO: <Victim Mail@hotmail.com>

data  

Subject: Test Fake Mail

Hi , I am Testing A fake mail

.

هل هناك أسهل من هذا ..ولهذا أطلق علي البروتوكول  Simple.طبعا يمكنك إضافة معلومات أخري إلى الرسالة مثل أسم المرسل وأسم المرسل إليه والتاريخ ونوع الرسالة... إلخ

لكننا قد كتبنا ما يسمح لنا بإرسال البريد فقط  وهذه الخطوات واحدة  للتعامل مع البروتوكول من أي برنامج  تعرفه يرسل  أيميل ويجب مراعاة تلك الخطوات والترتيب عندما نستخدمها نحن من داخل برنامجنا, الذي سنقوم بكتابته

وكما لاحظت فلابد أن نرسل hello لخادم البريد.. لكي نعرف هل سيستجيب لما سيرسل إليه أم لا؟  ثم كتابة البريد المرسل منه , ثم كتابة بريد المرسل إليه ثم كتابة جسم الرسالة ولبدء الكتابة نكتب Data ثم نكتب عنوان الرسالة فمضمون الرسالة ثم نقطة لإنهاء الرسالة.

وكل حدث تكتبه سيكون له رد من خادم البريد فمثلا إذا استقبلت :

220 :  مجرد معلومات عن خادم البريد الذي اتصلت به وتستقبل ذلك الرقم عند اتصالك مع خادم البريد SMTP Server .

250 :  يدل علي نجاح الخطوة  -(الأمر) - السابقة عند الخادم.

354 :  بدء كتابة جسم الرسالة.

221: يدل علي أن خادم البريد قد قطع الاتصال مع برنامجنا وهذا يحدث عندما ننهي اتصالنا بالخادم باستخدام Quit والمفروض أن عند تنفيذ Quit أن تكون الرسالة قد تم إرسالها.

451, 452, 500, 550, 551 : أخطاء غير متوقعه عند خادم البريد ..

 

 

 

 

 

أرقام MID موضحة كالأتي :

 

 

211 System status, or system help reply

214 Help message

[Information on how to use the receiver or the meaning

of a particular non-standard command;

this reply is useful only to the human user]

 

220 <domain> Service ready

221 <domain> Service closing transmission channel

 

[This may be a reply to any command if the service knows it must shut down]

250 Requested mail action okay, completed

251 User not local; will forward to <forward-path>

 

354 Start mail input; end with <CRLF>.<CRLF>

 

421 <domain> Service not available, closing transmission channel

452 Requested action not taken: insufficient system storage

450 Requested mail action not taken: mailbox unavailable [E.g., mailbox busy]

451 Requested action aborted: error in processing

 

500 Syntax error, command unrecognized

501 Syntax error in parameters or arguments

502 Command not implemented

503 Bad sequence of commands

504 Command parameter not implemented

550 Requested action not taken: mailbox unavailable

[E.g., mailbox not found, no access]

551 User not local; please try <forward-path>

552 Requested mail action aborted: exceeded storage allocation

553 Requested action not taken: mailbox name not allowed

[E.g., mailbox syntax incorrect]

554 Transaction failed

 

 

طيب في أثناء شرح دوال أل Winsock API  لم نذكر جميع دوال تلك المكتبة حيث هناك دوال أخري مهمة مثل دالة GetHostName ودالة gethostbyname  الأولي تأتي لك باسم الجهاز أما الثانية فعند إرسالك للبريد افترضنا أنك كتبت خادم البريد SMTP Server والذي ستتصل به عبر بروتوكول SMTP مثلا مثل موقع Hotmail في المثال السابق كتبنا له ملقم البريد  mail.hotmail.com- ولا أعلم لماذا أطلقوا عليه ملقم لكن المقصود به هو السيرفر Server-  وكل خادم بريد هو جهاز كومبيوتر في الواقع  متصل بالشبكة وله عنوان IP .. ولكي نتصل بهذا الخادم يجب أن نعرف عنوان أل IP لجهاز الخادم, ولذلك سنستخدم دالة gethostbyname والتي تتيح لنا معرفة رقم العنوان IP  بدلالة أسم ملقم الخادم SMTP Server.

 

 

وتصريح تلك الدالة :

 

Declare Function gethostbyname Lib "ws2_32.dll" (ByVal host_name As String) As Long              

 

إذا لم تنجح تلك الدالة فهي تعود بالقيمة صفر .

 

ستجد مثال جميل جداً في برنامج API Guide -وهو بالمناسبة من أفضل وأسرع وأسهل المصادر لدوال API -  والذي يمكنك تحميله من موقع www.allapi.net.

المثال ستجده بأسم gethostbyname وبه يعود بعنوان أل IP المحلي بدلالة اسم الجهاز واسم الجهاز تستطيع الحصول بواسطة الدالة gethostname,وهنا يلعب اسم الجهاز بالضبط دور اسم الموقع أو اسم خادم البريد.

 

نفس المثال بالضبط الذي ستحصل عليه من برنامج API Guide تستطيع أن تحصل عليه من مكانه الأصلي بمقاله في Microsoft Knowledge Base رقمها  160215 وبعنوان :

"How To Obtain the Host IP Address Using Windows Sockets"

أو انسخ العنوان التالي:

http://support.microsoft.com/default.aspx?scid=kb;EN-US;160215

 

 

المعامل الوحيد لدالة gethostbyname هو  host_name ونعوض عنه باسم الموقع أو اسم خادم البريد الذي نريد عنوانه.

وتستقبل تلك الدالة -إذا نجحت- معلومات عن عنوان الإنترنت المطلوب ومنها رقم أل IP وتخزن في بنية HOSTENT ولذلك تعتبر تلك الدالة من دوال Database Functions لأنها تحتاج إلى حجز Structure أو بنية لتخزين المعلومات.

 

Type HOSTENT

    hName As Long                      'أسم الموقع

    hAliases As Long                   ' Host مصفوفة يخزن بها أسماء أخري بديلة للموقع لو كان

    hAddrType As Integer           'عنوان العائلة للبروتوكول

    hLength As Integer                 'الطول بالبايت لكل عنوان

    hAddrList As Long                 ' أو خادمIP هذا لو كان الموقع  له أكثر من عنوان IP(s)قائمة بعناوين الانترنت

End Type

_________________________________________________________________________________

وقبل استخدام دالة gethostbyname يجب أن تلقي نظرة على كيفية جلب العنوان الذي سيخزن  في البنية  HOSTENT وأرجو أن تكمل الموضوع للنهاية - هذا في حالة إذا لم تكن قد تعاملت مع لغة السي, أو سبق لك التعامل مع المؤشرات Pointers في لغة الفيجوال بيسيك.. أما إذا سبق لك التعامل مع أي منهم فيمكنك ملاحظة الموضوع فقط والمرور عليه إلي بداية خطوات إرسال البريد-  .

 

فتلك البنية HOSTENT تعتبر في الواقع مؤشر أو (Pointer) .والمؤشر هو متغير يخزن في مكان في الذاكرة ويتكون المؤشر من اثنين . الأول قيمة المؤشر أو المتغير Pointer Value والثاني موقعه في الذاكرةPointer Address  .

 

وعند استخدام دالة  gethostbyname بنجاح فهي تعود بمؤشرات أو بمتغيرات تحتوي على معلومات للموقع المطلوب  تخزن في البنية HOSTENT, وضمن تلك المعلومات سنجد عنوان أل IP للموقع.

ولكي يتم الحصول علي عنوان أل IP من المؤشر الموجود في الذاكرة يجب استخدام دالة تتعامل مع الذاكرة مثل CopyMemory .

 

وأرجو أن تأخذ بالك من تلك النقطة لكي نكون أكثر تحديدا :

 

دالة  gethostbyname لا تعود بمعلومات بنية HOSTENT ولكنها تعود بمؤشر (Pointer) لتلك البنية ..

 

أي أننا لا نستطيع أن نقول للحصول علي عنوان ال IP :

strIP= HOSTENT.hAddrList

 

 وفي الأصل هذا المؤشر يشير إلى عنوان القيم التي عادت من جراء استخدام الدالة gethostbyname (ومنها عنوان أل IP) الذي نريده.

وهكذا تتخذ المؤشرات شكل القوائم المتصلة Linked list

 

وسيكون عنوان أل IP للخادم  مخزن في المؤشر  hAddrList  من بنية HOSTENT وكما قلت سابقا سنستخدم دالة مثل CopyMemory أو RtlMoveMemory .

 

 

 

وتصريح تلك الدالة :

Declare Sub CopyMemory Lib "kernel32.dll" Alias "RtlMoveMemory" (Destination As Any, Source As _  Any, ByVal Length As Long)

 

 

وتستخدم تلك الدالة لنقل جزء من الذاكرة من مكان لأخر.وطبعا المكانين سنعوض عنهم في عناوين الذاكرة  بالمؤشرات .

====================================================================================

Destination  :

مؤشر لعنوان الذاكرة والذي سيستقبل البيانات المنقولة إليه .

====================================================================================

Source :

مؤشر لعنوان الذاكرة والذي سيتم منه نقل البيانات إلى المؤشر الأول Destination .

====================================================================================

Lenght :

عدد الباتات (Bytes) التي سيتم نقلها من أل Source إلى أل Destination .

====================================================================================

 

 

وكمثال سريع فقط علي استخدام دالة CopyMemory :

 

 

Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long)

 

Private Sub Command1_Click()

 

  Dim A As Integer

  A = 5000

  CopyMemory A, 32000, 2

 MsgBox A

 

End Sub

في المثال السابق إذا قمت بتنفيذه فستجد قيمة المتغير A قد أصبحت ب 32000 وذلك لأننا تعاملنا معه علي أنه مؤشر وغيرنا قيمته من الذاكرة باستخدام دالة CopyMemory وبدون وضع "A=32000" .

 

ولاحظ أننا وضعنا حجم البيانات التي نريد نقلها ب 2 ويمكن تمثيل ال 2 بايت بقيمة في حدود حجم المتغير من نوع Integer لوضعها في متغير من نوع Integer وهو A, ولكن إذا زادت القيمة من 32000 إلي مثلا 40000 فسيحدث Overflow وسيأخذ المتغير قيمة ليست قيمته الحقيقة وبالتالي ينبغي تغيير حجم كلا من المتغير A ليصير Long بحجم 4 بايت.. وكذلك تغيير حجم البيانات المنتقلة ليصير 4 بايت.

 

==============================================================

وسأقوم الآن بشرح كيفية تمثيل أنواع البيانات المختلفة في الذاكرة وكيفية معرفة عنوان الذاكرة لمتغير ما:

ولاحظ أن هذا الكلام مهم جدا حتى تستطيع فهم المؤشرات Pointer

==============================================================

 

 

استخدام VarPtr :

 

دالة VarPtr أو Variable Pointer تستخدم للحصول علي عنوان الذاكرة  Address Memory لمتغير رقمي أو لعنصر في مصفوفة Array element رقمية ولها معامل Parameter وحيد ويعوض عنه باسم المتغير أو عنصر المصفوفة والذي نريد معرفة عنوانه في الذاكرة..

 

مثال :

 

 

Private Sub Form_Click()

 Dim lngVarAddress As Long

 Dim intVar As Integer

  lngVarAddress = VarPtr(intVar)

  Print "Address Memory of the Variable intVar is : " ;lngVarAddress

End Sub

 

 

 

في المثال السابق عرفنا متغير وليكن lngVarAddress من نوع Long أي أنه 4 بايت أي أنه يستطيع حمل أرقام  signedفي المدي :

 

Long Variable Signed : -2147483648 to 2147483647

 

وهذا لأن عناوين الذاكرة تكون أرقام كبيرة نوعا.. أكبر من أن يتحملها متغير من نوع Integer  فسيحدث Overflow  لأن مداها :

 

Short Or Integer Variable signed: -32768 to 32767

 

 

المهم بعد ذلك عرفنا متغير intVar من نوع Integer .. وهذا المتغير هو ما سنقوم بمعرفة عنوان الذاكرة له عن طريق استخدام الدالة VarPtr..ثم قمنا بطباعة عنوان الذاكرة له علي الفورم... والصورة التالية ستكون شبيهة بما سيحدث عندك..

 

 

 

الآن سنقوم بمعرفة عناوين الذاكرة لمصفوفة Fixed عدد حدودها عشرة وهذا لسبب ستعرفه في حينه :

 

المثال التالي يقوم بطباعة عناوين الذاكرة لمصفوفة MyArray(9) من نوع integer علي الفورم :

 

 

 

Private Sub Form_Click()

 Dim lngVarAddress As Long

 Dim MyArray(9) As Integer

 For I = LBound(MyArray) To UBound(MyArray)

  

    lngVarAddress = VarPtr(MyArray(I))

    Print "Address Memory of the MyArray("; I; ")"; " is : "; lngVarAddress

 

 Next I

End Sub

 

 

بعد تنفيذ الكود لاحظ الصورة التالية ثم قم بالاستنتاج :

 

 

 

بالضبط كما استنتجت أنت .. عندما حجزنا مصفوفة من نوع Integer – 2 بايت- وعدد حدودها عشرة.. قام بحجز 20 بايت في الذاكرة.. وقد قمنا بمعرفة عناوين عناصر تلك المصفوفة في الذاكرة حسب أل Base  ...

 

وإذا كانت المصفوفة من نوع Long  - 4 بايت- مثلما توجد في هذا الكود  :

 

 

 

Private Sub Form_Click()

 Dim lngVarAddress As Long

 Dim MyArray(9) As Long

 For I = LBound(MyArray) To Ubound(MyArray)

  

    lngVarAddress = VarPtr(MyArray(I))

    Print "Address Memory of the MyArray("; I; ")"; " is : "; lngVarAddress

 

 Next I

End Sub

 

 

فسيتم حجز 40 بايت :

 

 

========================================

كل ما سبق حتى الآن جميل ...بل غاية في الجمال.. لكن ماذا سنستفيد من كل هذا ؟

 

الصراحة لا أعلم إذا كانت تحسب علي لغة الفيجوال بيسيك سهولة التعامل مع المتغيرات والمصفوفات من وإلي الذاكرة ..حتى أن هناك الكثير لم يسمع أبدا عن وجود المؤشرات Pointers في لغة الفيجوال بيسيك.. لكن أعتقد أن هذا أفضل وأكثر سهولة لهذه اللغة وسنأخذ المثالين التاليين لتعرف الفرق :

 

 

سنعمل علي مثال المصفوفة ذات الحدود العشرة السابقة مع تحوير بسيط :

 

المثال الأول:

 

نقوم فيه بإسناد قيم للمصفوفة وإرجاع تلك القيم بالطريقة التقليدية :

 

 

 

 

 

 

Private Sub Form_Click()

 Dim lngVarAddress As Long

 Dim MyArray(9) As Long

 For I = LBound(MyArray) To Ubound(MyArray)

   

    MyArray(I) = I

    lngVarAddress = VarPtr(MyArray(I))

    Print "Address Memory of the MyArray("; I; ")"; " is : "; lngVarAddress ;

    Print "| Value = " ; MyArray(I)

 

 Next I

End Sub

 

 

 

 

 

 

 

المثال الثاني :

 

نقوم فيه بإسناد القيم للمصفوفة وإرجاعها عن طريق استخدام دالة CopyMemory  والتي سنقوم بنسخ محتوي المؤشر  أو عنوان الذاكرة إلي متغير لدينا :

 

 

 

 

Private Sub Form_Click()

 Dim lngVarAddress As Long

 Dim MyArray(9) As Integer

 Dim intVal As Integer

 

 For I = LBound(MyArray) To UBound(MyArray)

 

    MyArray(I) = I

    lngVarAddress = VarPtr(MyArray(I))

    Print "Address Memory of the MyArray("; I; ")"; " is : "; lngVarAddress; " ";

    CopyMemory intVal, ByVal (lngVarAddress), 2

    Print "|  Value = "; intVal

 

 Next I

End Sub

 

 

 

 

ونفس الصورة السابقة ستظهر مع اختلاف عناوين الذاكرة (عناوين المؤشرات) كل مرة  :

 

 

 

لاحظ هنا أنه عندما استخدمنا دالة CopyMemory وضعنا لها عنوان الذاكرة (عنوان المؤشر) lngVarAddress في المعامل الثاني والذي نريد نسخ محتوي بياناته من عنصر مصفوفة حجمه 2 بايت إلي متغير من نوع Integer حجمه 2 بايت وهو  intVal ولذلك وضعنا حجم البيانات المراد نسخها  2 بايت لأنه حجم عنصر المصفوفة element  المنسوخ منه البيانات .

 

سأضع الآن مثال علي كيفية نسخ حدود مصفوفة من الذاكرة إلي مصفوفة أخري... وأرجو أن تركز في هذا المثال جيدا ... لأنك ستستخدم نفس الفكرة فيما بعد ..

 

اسم المثال سيكون (نسخ مصفوفة من الذاكرة) وهذا لأنني سأذكرك بالرجوع إلي هذا المثال فيما بعد..

 

المهم شفرة المثال ستكون كالأتي :

 

 

 

Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" ( Destination As Any, Source As Any, ByVal Length As Long)

 

Private Sub Form_Click()

 

    Dim Array1(9) As Long             ' Original first Array

    Dim z As Long                         ' just a counter

    Dim Array2() As Long              ' Second Array that will hold data of Array1 in it

   

 

    ReDim Array2(9)

 

' <<  setting some values to Array1 >>

 

    For z = 0 To UBound(Array1)

        Array1(z) = 2 * z

    Next

 

' << Copy data from Array1 to Array2 >>

 

    CopyMemory Array2(0), Array1(0), 40

 

' << Print the bounds values  of Array2 >>

 

    Debug.Print String(19, "=")

    Debug.Print "Array1" & Space(5) & "Array2"

    Debug.Print String(19, "=")

    Debug.Print    ' Free line

   

    For z = 0 To UBound(Array2)

        Debug.Print Space(3); Array1(z); Space(5); Array2(z)

    Next z

    

End Sub

 

 

 

 

المثال بسيط جدا.. وفيه قمنا بتعريف مصفوفة Array1 من 10 حدود من نوع Long أي 4 بايت لقيمة الحد, معني ذلك كما استنتجت أنت وبحسبة بسيط  (4*10) أن المصفوفة قد أخذت مساحة قدرها 40 بايت لـ 10 حدود , وقمنا بتعريف مصفوفة أخري Array2 أيضا من 10 حدود ومن نوع Long

 

وبعد ذلك قمنا بوضع قيم في المصفوفة Array1 حتى لا تكون قيم الحدود بصفر .

 

ثم قمنا بنسخ حدود المصفوفة Array1 إلي Array2 بواسطة الكود التالي :

  CopyMemory Array2(0), Array1(0), 40

 

لاحظ أننا وضعنا حجم البيانات المراد نسخها 40 بايت, وبالتالي فسيتم نسخ القيم منذ أول بايت في المصفوفة Array1 حتى أخر بايت في أخر حد للمصفوفة Array1 ..

 

لاحظ أيضا أن المصفوفة Array2 قمنا بحجز 40  بايت لها أيضا في الذاكرة... وبالتالي فسيتم نسخ جميع حدود المصفوفة  Array1 إليها...

 

لاحظ أيضا أننا إذا أردنا نسخ خمس حدود فقط إلي المصفوفة Array2 ..فسيكون حجم البايتات المراد نسخها 20 وهكذا...

 

لاحظ أيضا أنك فيما بعد ستحتاج إلي نسخ ال IP في هيئة مصفوفة من الذاكرة.. ولهذا كتبت هذا المثال...

 

======================

كان يمكننا بالطبع أن نعوض عن Array1(0) في الكود :

 

CopyMemory Array2(0), Array1(0), 40

نعوض عنه بعنوان الذاكرة (المؤشر) لأول حد في المصفوفة Array1(0) ..

 

هل تستطيع أن تأتي بعنوان الذاكرة لأول حد في تلك المصفوفة ؟

 

أكيد ستستخدم دالة VarPtr هكذا :

CopyMemory Array2(0), VarPtr(Array1(0)) , 40

 

انتظر لحظة فالكود السابق به خطأ شديد .. هل استنتجته ؟

 

هل نسيت أنك تعوض بقيمة المؤشر وليس بعنوان المؤشر وبالتالي فلابد من وضع ByVal ليصبح :

 

CopyMemory Array2(0), ByVal (VarPtr(Array1(0))), 40

 

 

المهم أنه باستخدام أي طريقة لابد أن تظهر لك تلك الصورة في شاشة ال Immediate window

 

 

ومن ناحية الاستفادة الفعلية من دالة CopyMemory في موضع إرسال البريد فهي ستستخدم في آتيان عنوان الإنترنت IP من المؤشر فكما قلت سابقا عندما نستخدم دالة GetHostByName فهي تعود بمؤشر . سنقوم نحن بنسخ هذا المؤشر بعناصره والذي عادت به الدالة إلى البنية HOSTENT باستخدام دالة CopyMemory حتى نستطيع استخلاص عنوان الأنترنت من داخل البنية HOSTENT ...

 

والإجراء التالي يقوم باستخلاص عنوان الانترنت IP من البنية HOSTENT ولاحظ أنني سأضع الكود أولا ثم أشرحه وأرجو أن تلقي نظرة علي الكود:

 

ملحوظة : يوجد مع المرفقات برنامج لإتيان عنوان الانترنت IP لأي SMTP Server .

 

Public Function GetIPAddress(sHostName As String) As String

  Dim lpHost    As Long

  Dim HOST      As HostEnt

  Dim dwIPAddr  As Long

  Dim tmpIPAddr() As Byte

  Dim sIPAddr  As String

 

 

  sHostName = Trim$(sHostName)

 1 lpHost = gethostbyname(sHostName)

 

     If lpHost = 0 Then

      GetIPAddress = ""

      MsgBox "Unable to successfully get Host Name..May be The SMTP Server Is Not Exist.." & _

               "Or You are Offline ."

      Exit Function

   End If

  

 2   CopyMemory HOST, lpHost, Len(HOST)

 3   CopyMemory dwIPAddr, HOST.h_addr_list, 4

  

 4   ReDim tmpIPAddr(1 To 4)

   

 5  CopyMemory tmpIPAddr(1), ByVal (dwIPAddr), 4

  

  |    For i = 1 To 4

 6|      sIPAddr = sIPAddr & tmpIPAddr(i) & "."

  |    Next

     

 7  GetIPAddress = Mid$(sIPAddr, 1, Len(sIPAddr) - 1)

  

  

End Function

ولاستدعاء الإجراء السابق نكتب مثلا :

 

Call GetIPAddress("mx1.mail.yahoo.com")   

 

 

 

============================================================================

 

تعالي الآن نرتب معلوماتنا في نقط مبسطة والخاصة بإتيان أل IP لأي موقع:

 

<  بالتالي إذا لم تنجح الدالة لأي سبب فستعود بالقيمة صفر... وبالتالي يمكننا الخروج من الإجراء بــ Exit Function

      <  أما إذا نجحت فستعود بمؤشر lpHost يحتوي علي مؤشرات داخلية متفرعة ..

 

 

الآن أصبحت عندنا مؤشرات البنية HOST وقبل الغوص في شرح أي سطور أخري ينبغي معرفة الآتي :

 

 

هناك ثلاث مؤشرات يجب المرور عبرهم للوصول إلي عنوان الانترنت IP ..

 

أول مؤشر وهو مؤشر البنية HOST نفسها                                      أفترض أنه يساوي 8385144

ثاني مؤشر وهو مؤشر HOST.h_addr_list                                     أفترض أنه يساوي 8385156

 

هل لاحظت الفرق بين قيمتي المؤشرين... ستجده دائما رقم ثابت وهو 12 وبالتالي من الممكن أن نصل إلي قيمة المؤشر HOST.h_addr_list بزيادة 12 علي قيمة المؤشر Host .. – المهم أنه ليس موضوعنا الآن-

 

ثالث مؤشر وهو في الواقع مؤشر إلي أول حد في عنوان ال IP والذي يحتوي علي أربع حدود بحجم 4 بايت .. ولاحظ اننا وصلنا لعنوان أول حد وبالتالي أصبح عندنا عنوان بقية الحدود لعنوان ال IP في الذاكرة .. معني هذا أننا إذا قمنا بنسخ تلك الأربع بايتات في مصفوفة عندنا فستحتوي تلك المصفوفة علي عنوان ال IP  ..

وبالتالي سننسخ تلك المصفوفة إلي مصفوفة لدينا tmpIPAddr من نوع بايت بواسطة دالة CopyMemory.. وسنقوم بتجميع عنوان ال IP في صوره Dotted , بعمل تكرار داخل حدود المصفوفة tmpIPAddr وتجميعه في صورة (000.000.000.000)....

 

والسطر 7 نقوم فيه بحذف أخر نقطة والتي ستتكون في عنوان ال IP نتيجة لإضافة نقطه بعد كل حد في المصفوفة tmpIPAddr .., وكان يمكننا طبعا استخدام دالة Left وستكون أسهل ..

 

 

وقم بالرجوع إلي مثال  (نسخ مصفوفة من الذاكرة) لمزيد من الفهم ..وستجد هنا أننا نسخنا الأربع بايتات ابتداء من عنوان الذاكرة dwIPAddr وبحجم   4 Bytesوهو حجم ال IP في الذاكرة ......... تماما مثلما فعلنا في مثال نسخ مصفوفة ذات عشر حدود من الذاكرة إلي مصفوفة أخري ...

 

 

وكان من الممكن طبعا نسخ كل بايت علي حده هكذا :

 

أحذف السطر الرابع وما بعده وضع الأتي :

 

 

ReDim tmpIPAddr(0)

   

   CopyMemory tmpIPAddr(0), ByVal (dwIPAddr), 1

          sIPAddr = sIPAddr & tmpIPAddr(0) & "."

  

   CopyMemory tmpIPAddr(0), ByVal (dwIPAddr + 1), 1

         sIPAddr = sIPAddr & tmpIPAddr(0) & "."

  

   CopyMemory tmpIPAddr(0), ByVal (dwIPAddr + 2), 1

         sIPAddr = sIPAddr & tmpIPAddr(0) & "."

  

   CopyMemory tmpIPAddr(0), ByVal (dwIPAddr + 3), 1

          sIPAddr = sIPAddr & tmpIPAddr(0)

 

 GetIPAddress = sIPAddr

 

 

 

أكيد فهمت ما قمت بعمله.. فكل بايت من الأربع بايتات للـ IP في الذاكرة له عنوان مؤشر يزيد كما تعرف عن عنوان المؤشر الذي قبله... كل ما فعلته هو أن نسخت كل بايت علي حدة بدلالة عنوان مؤشره والذي يزيد بمقدار واحد عن ما قبله وبحجم بيانات بايت واحد ..

 

 

والصورة التالية تلخص كل شيء ... علي فرض أننا سنأتي بال Loopback IP وهو 127.0.0.1 ..

 

 

 

 

أعتقد أن أي التباس قد زال الآن ويمكننا الانتقال إلى استكمال كتابة البرنامج

لكن قبل أن نستكمل فلنلقي نظرة علي بنية HOSTENT في لغة السي حتى نستنتج معلومة أخيرة في هذا المجال

 

 

struct hostent {

    char FAR *       h_name;

    char FAR * FAR * h_aliases;

    short            h_addrtype;

    short            h_length;

    char FAR * FAR * h_addr_list;

};

 

 

علامة * تعني  مؤشر في لغى السي, لاحظ المتغير h_addr_list في البنية السابقة.. حيث ستجد أنه مؤشر * إلي مؤشر *  إلي char وحجم ال char هو واحد بايت ...

 

هل سألت نفسك ما الداعي وراء وجود مؤشرين  للوصول إلي عنوان الانترنت  IP ؟.. فقد كان من الممكن أن يجعلوا الوصول إلي عنوان الانترنت عن طريق مؤشر واحد هو h_addr_list ..

 

في الواقع إذا نظرت إلي مواقع مثل www.hotmail.com ,  www.yahoo.com,www.cnn.com ستجد أن ال DNS لأي منهم له أكثر من عنوان انترنت IP ... فكيف إذن سنأتي بجميع عناوين الانترنت لموقع وليكن www.yahoo.com

 

 

 وبالتالي دعت الحاجة إلي أن يكون h_addr_list مصفوفة من نوع Long أي 4 بايت لكل حد لها .. ويخزن بتلك المصفوفة جميع مواقع الانترنت IPs لأي DNS  في الذاكرة .

 

بما أن h_addr_list تعتبر مصفوفة.. فالمصفوفة في أبسط معانيها -وأي مصفوفة في الواقع- تكون مؤشر إلي حدودها.. وهذا يوضح لماذا استخدمنا دالة CopyMemory للمرة الثالثة, لاستخلاص الحد الأول في المصفوفة.

 

فمثلا أفترض الأتي :

 

المؤشر h_Addr_list عنوانه هو   (8485140), وكما قلت سابقا .. فبما أنه مصفوفة فالمصفوفة تعتبر مؤشر إلي حدودها فأول حد في تلك المصفوفة هو ال IP الأول الأفتراضي والرئيسي للموقع ولتفترض أن مؤشره هو (8485156).. ومنه سننسخ الأربع بايتات لعنوان الانترنت IP :

 

 

h_addr_list           dwIPAddr 

                        >>>>>>>>>    

  (8485140)                            (8485156)           

 

ثاني حد للمصفوفة h_Addr_list وهو سيكون عنوان الانترنت الثاني  للموقع Second IP Address ونحصل عليه بإضافة أربع بايتات إلي عنوان مؤشر h_Addr_list بما أنها من نوع Long أي 4  بايت لكل حد لتلك المصفوفة :

 

h_addr_list + 4            dwIPAddr 

                                >>>>>>>>>    

     (8485144)                                  (8485180)

 

ونحصل علي عنوان الانترنت الثاني للموقع IP بنفس خطوات الحصول علي عنوان الانترنت الأول,و ذلك بنسخ 4 بايتات ابتداء من عنوان الذاكرة للحد الثاني وفي المثال السابق سيكون (8485180)..

 

وهكذا نظل في زيادة أربعة بايتات علي عنوان المؤشر h_Addr_list لكي نأتي بجميع عناوين الانترنت للموقع..

 

 

هكذا نكون قد انتهينا من موضوع إتيان عنوان الانترنت للـ SMTP Server.

 

 

لم يعد هناك أي مشكلة في إرسال البريد الإلكتروني , فسنعمل اتصال عادي جدا علي IP للخادم وعلي المنفذ 25 ومن ثم نرسل له محتويات البريد الإلكتروني بدالة Send () وننتظر رد الخادم .

 

أولا سنكتب أكواد الاتصال مع خادم البريد المراد تمرير رسالة عبره إلى User مشترك علي الخادم:

ولاحظ أنني دمجت أمر الاتصال مع أمر إرسال أل أيميل وذلك تحت Command يدعي CmdSend

لاحظ أيضا أن علي المشروع سيكون هناك Text تدعي txtStatus وسنستعرض بها مراحل إرسال البريد .

سيكون هناك أيضا 4 Text  هم txtServer و txtMail وtxtSubject وtxtBody وهم على الترتيب , الأولي لكتابة عنوان الخادم

 (Mail SMTP Server) والثانية لكتابة أسم البريد الذي سترسل له الرسالة , والثالثة والرابعة لعنوان ومضمون الرسالة .

 

لاحظ أيضا أننا سنستخدم موضوع أل Hook  وال SubClass وستجد أنني عملت خطاف للرسائل وأرسلت الرسائل إلى التصنيف الفرعي يدعي WinsockMain وسيتم تحليل رسائل المقبس بالضبط كما عرفنا من دروس تعلم Winsock API .

 

لاحظ أيضا أنني عملت أجراء فرعي لارسائل الرسائل النصية يدعي SendText ومررت له 3 وسيطات الأولي أسم البريد txtmail المرسل له والثانية عنوان الرسالة txtSubject والثالثة مضمون الرسالة  TxtBody.

 

 

Private Sub CmdSend_Click()

 

Dim RemoteAddr As sockaddr_in

On Error Resume Next

 

   LlistenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)

   If LlistenSocket = INVALID_SOCKET Then

      txtStatus = txtStatus & vbCrLf & "Unable to Create Socket"

   Else

 

      DoEvents

      txtStatus = txtStatus & vbCrLf & "Trying To Connect"

      RemoteAddr.sin_family = AF_INET

      RemoteAddr.sin_port = htons(CLng(25))

      RemoteAddr.sin_addr = inet_addr(GetIPAddress(txtServer))

      lRetval = connect(LlistenSocket, RemoteAddr, Len(RemoteAddr))

      If lRetval = SOCKET_ERROR Then

         txtStatus = txtStatus & vbCrLf & "Unable To Connect"

        Exit Sub

     Else

       txtStatus = txtStatus & vbCrLf & "Connected  Success TO " & txtServer

     End If

    MsgBox "LlistenSocket =" & LlistenSocket

      WSAAsyncSelect LlistenSocket, Form1.hwnd, ByVal WM_SOCKET, _

                     ByVal FD_CONNECT Or FD_READ Or FD_CLOSE

   End If

 

'*****************************************

SendText txtmail, txtSubject, txtbody   'أجراء فرعي لإرسال الرسالة

 

End Sub

 

الآن سنتناقش سويا مع أوامر البروتوكول وكيفية الاستفادة من أرقام MID .

نحن طبعا لن نستطيع إرسال أمر معين إلى خادم البريد طالما لم يتم تنفيذ الأمر الذي يليه فمثلا أثناء كتابتنا لأول مثال باستخدام Telnet

كتبنا أيميل المصدر للرسالة وهو :

MAIL FROM: <Thensync@hotmail.com>

ولم نكتب الخطوة التالية وهي كتابة البريد الذي سنرسل له الرسالة إلا عندما رد علينا خادم البريد بالرسالة التالية

250 Thensync@hotmail.com... Sender ok

ومعناها أن الخطوة تمت بنجاح ويمكنك الانتقال إلى كتابة الخطوة التالية:

RCPT TO: <Victim Mail@hotmail.com>

 

المهم الآن كيف سنعرف في برنامجنا أن الخادم رد علينا  علي رسالة معينة أم لا ؟

فبكل بساطة يمكننا عمل تصنيف لأرقام أل MID التي تسبق الرسالة مثل 250 و354 و220 و221 وتلك هي الأرقام التي تدل علي نجاح الأمر الذي أرسلته للخادم وخلاف تلك الأرقام يدل علي أن العملية قد فشلت Failed. 

بحيث إن مرحلة إرسال الرسالة ستقسم إلى 6 مراحل  وسنعرف متغير وليكن Step ومن نوع Integer وسيحمل أرقام المراحل الستة  لينفذها بالترتيب وكل مرحلة سيتم تنفيذها أثناء استقبال مقبس برنامجنا أي رقم MID يدل علي نجاح الخطوة السابقة  سنضيف إلى المتغير Step الرقم واحد وبالتالي سيتم تنفيذ المرحلة التي تليها  وهكذا إلى أن يتم تنفيذ أخر مرحلة فنغلق المقبس ونسند القيمة صفر إلى المتغير Step تمهيدا لإرسال رسالة أخري .

شفرة التصنيف الفرعي WinsockMain ستكون كالأتي :

 

Public step As Integer

 

Public Function Winsockmain(ByVal hwnd As Long, ByVal uMsg As Long, _

                         ByVal wParam As Long, ByVal lParam As Long) As Long

On Error Resume Next

If uMsg = WM_SOCKET Then

Dim ReadBuffer(1024) As Byte

Dim Data As String

Select Case lParam

  Case FD_CONNECT

        txtStatus = txtStatus & vbCrLf & "Connected"

  Case FD_READ

     

       lRetval = recv(wParam, ReadBuffer(0), 1024, 0)

       If lRetval > 0 Then

          Data = Left(StrConv(ReadBuffer, vbUnicode), lRetval)

******************************************* '

        'TxtStatusسيتم وضع رسائل خادم البريد المستقبلة في

         Form1.txtStatus = Form1.txtStatus & vbCrLf & Data

       

             Select Case Mid(Data, 1, 3)         'MID تحليل أرقام

         

            Case 220

              step = step + 1

              Form1.txtStatus = Form1.txtStatus & vbCrLf & "220 The Server is" & _                                                                                

                                               "Ready To Send Mail" & vbCrLf

              Form1.txtStatus = Form1.txtStatus & vbCrLf & "step Become " & step

             

           Case 250, 354

              step = step + 1

              Form1.txtStatus = Form1.txtStatus & vbCrLf & "step Become " & step

           

           Case 221   'Note That If Our Socket Recieved 221 that mean Mail Sent

              step = step + 1

              Form1.txtStatus = Form1.txtStatus & vbCrLf & "step Become " & step

           

            Case 451, 452, 500, 550, 551, 552, 553, 554  'Other MID Errors

                    MsgBox "The Program Connected But There Is Error At The" & _                                                                                                                            

                                  Form1.txtServer & " Mail Server " & vbCrLf & _

                                  "Error Code Number Is :" & Mid(Data, 1, 3)

               

        End Select

'*******************************************

       End If

  Case FD_CLOSE

      Form1.txtStatus = Form1.txtStatus & vbCrLf & "Disconnected"

  End Select

End If

 

Winsockmain = CallWindowProc(lpWinsock, hwnd, uMsg, _

                          wParam, lParam)

End Function

وبالنسبة للأجراء الفرعي SendText فقبل أن نكتبه لازم نحول البيانات من نصوص إلى مصفوفة بايتات Byte Array ANSI حتى يستطيع خادم البريد التعامل مع البيانات المرسلة. وعبر هذا الأجراء أيضا سنرسل الأوامر إلى خادم البريد.

هناك دالة تدعي VarType أو Varible Type وتفيد تلك الدالة في معرفة نوع المتغير (String , Integer ... الخ) عن طريق إرجاع قيمة تدل عليه. فمثلا إذا استخدمنا دالة VarType لمعرفة نوع متغير وعادت قيمة الدالة بالرقم 2 فهذا يعني أن المتغير من نوع Integer . والأرقام التالية توضح بعض قيم الأنواع المختلفة للمتغيرات .

2 =  Integer

3 =  Long

4 = Single

5 = Double

8 =String

11 =  Boolean

12 = Variant

17 = رقم صحيح Byte

36 = متغير معرف  باستخدام Type

8192 = مصفوفة Array

ونستطيع أن نعرف نوع المتغير ومن ثم نقوم بتحويله إلي مصفوفة بايتات ولاحظ أننا لن نحتاج سوي لتحويل قيم Strings وهي القيم التي سنرسلها لخادم البريد :

 

Public Function SendData(ByVal Sock&, Message As String) As Long

 

    Dim TheMsg() As Byte

     TheMsg = ""

     TheMsg = Message

     TheMsg = StrConv(Message, vbFromUnicode)

     

 

   

    Debug.Print TheMsg; " | "; StrConv(TheMsg, vbUnicode); " | "; UBound(TheMsg)

       

        If UBound(TheMsg) > 0 Then

            SendData = send(Sock, TheMsg(0), UBound(TheMsg) + 1, 0)

        End If

 

End Function

الأجراء التالي وهو الخاص بإرسال الأوامر بالطلبات وتلك الأوامر تدعي Header وهذا أل Header له ترتيب خاص لإرسال معلومات الرسالة والترتيب التالي يوضح ما أقول :

1- HELO anyThing   'Enter  وتستخدم كبروتوكول لبدء كتابة الرسالة ثم

2- MAIL FROM: <Thensync@hotmail.com>    ' Enterثم    

3-RCPT TO: <AnyMail@hotmail.com>        ' Enterثم    

4-DATA                                  'Enterثم    

5-

  From: Mohamed Fayed       'أسم المرسل وهي معلومة إضافية ويمكن الاستغناء عنها

  To: Receiver Name         'أسم متلقي الرسالة وأيضا معلومة إضافية   

  SUBJECT: Test Smtp Mail   ' ونكتب نص الرسالةEnter عنوان الرسالة ثم

           That Is My Smtp Send Mail        ' Mail Body

  .                         'Enter نقطة لإنهاء الرسالة حتى يتم إرسالها ثم

6-quit                      'Enterالخروج ثم   

 

 

وهناك معلومات إضافية أخري يمكن كتابتها مثلا مثل التاريخ والوقت ..

 

المهم هذا هو الأجراء الخاص بإرسال أل أيميل:

 

Public Sub SendText(Mailto As String, MailSubject As String, MailBody As String)

   

Msg = "HELO " & Mid(Mailto, InStr(1, Mailto, "@") + 1) & vbCrLf

 Call SendData(LlistenSocket, "HELO " & Mid(Mailto, InStr(1, Mailto, "@") + 1) & vbCrLf) 'send the data

Form1.txtStatus = Form1.txtStatus & Msg

   While step < 1    ' لتضييع الوقت لحد ما يتم تنفيذ كود الإرسال وهو يعتمد عليloopمجرد

        DoEvents     ' واحد وينتقل Stepتلقي رد من الخادم.فإذا تلقي رد فسيضيف إلى المتغير

  Wend               'لتنفيذ الكود التالي ويمكنك وضع مؤقت لكي يعرف المستخدم بأن فترة

                     ' الانتظار وهي في معظم البرامج تقدر ب 60  ثانية قد طالت وأنه خادم

                     ' البريد لا يرد علي الأوامر المرسلة إليه

'*****************************************

 

Msg = "MAIL FROM: <" & "Thensync@hotmail.com" & ">" & vbCrLf

 Call SendData(LlistenSocket, "MAIL FROM: <" & "Thensync@hotmail.com" & ">" & vbCrLf)

  Form1.txtStatus = Form1.txtStatus & Msg

 

  While step < 2

        DoEvents

   Wend

'*****************************************

Msg = "RCPT TO: <" & "nanosoftegypt@yahoo.com" & ">" & vbCrLf

Form1.txtStatus = Form1.txtStatus & Msg

Call SendData(LlistenSocket, "RCPT TO: <" & "nanosoftegypt@yahoo.com" & ">" & vbCrLf)

 

  While step < 3

        DoEvents

  Wend

'*****************************************

 

Msg = "DATA" & vbCrLf

Form1.txtStatus = Form1.txtStatus & Msg

Call SendData(LlistenSocket, "DATA" & vbCrLf)

 

  While step < 4

        DoEvents

  Wend

 '########################################

Msg = "SUBJECT: " & MailSubject & vbCrLf

Form1.txtStatus = Form1.txtStatus & Msg

Call SendData(LlistenSocket, Msg & vbCrLf)

'===========================

Msg = MailBody & vbCrLf

Form1.txtStatus = Form1.txtStatus & Msg

Call SendData(LlistenSocket, Msg & vbCrLf)

 '==========================

Msg = "." & vbCrLf

Form1.txtStatus = Form1.txtStatus & Msg

Call SendData(LlistenSocket, Msg & vbCrLf)

'########################################

Msg = "quit" & vbCrLf

Form1.txtStatus = Form1.txtStatus & Msg

  

Call SendData(LlistenSocket, Msg & vbCrLf)

   While step < 5

        DoEvents

  Wend

 

  closesocket (LlistenSocket)

MsgBox "Email has been Sent To " & Form1.txtmail

 

End Sub

 

<<< The END >>>

 

Tutorial By:

Mohamed Farouk Fayed

 

Thensync@hotmail.com

Nanosoftegypt@yahoo.com